﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Threading;

namespace DaveSexton.Labs
{
	public partial class WindowSettings
	{
		#region Private / Protected
		private Window trackedWindow;
		private Rect trackedLocation;
		private DispatcherOperation trackingLocation;
		#endregion

		#region Constructors
		private WindowSettings()
		{
			var app = Application.Current;

			if (app != null)
			{
				app.Exit += (sender, e) =>
					{
						if (trackedWindow != null)
						{
							Save();
						}
					};
			}
		}
		#endregion

		#region Methods
		[DebuggerHidden]
		public override void Save()
		{
			if (trackedWindow != null)
			{
				Location = trackedLocation;

				if (trackedWindow.WindowState == WindowState.Minimized)
				{
					State = WindowState.Normal;
				}
				else
				{
					State = trackedWindow.WindowState;
				}
			}

			base.Save();
		}

		internal void Track(Window window)
		{
			Contract.Requires(window != null);

			if (trackedWindow != null)
			{
				trackedWindow.SizeChanged -= SizeChanged;
				trackedWindow.LocationChanged -= LocationChanged;
				trackedWindow.StateChanged -= StateChanged;
			}

			trackedWindow = window;

			SetLocation(trackedWindow, Location);
			SetState(trackedWindow, State);

			trackedWindow.SizeChanged += SizeChanged;
			trackedWindow.LocationChanged += LocationChanged;
			trackedWindow.StateChanged += StateChanged;
		}

		private static void SetLocation(Window window, Rect restoreTo)
		{
			Contract.Requires(window != null);

			// The default value is {0,0,0,0}, which is not actually equal to Rect.Empty.
			if (restoreTo == new Rect())
			{
				window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
				return;
			}
			else
			{
				window.WindowStartupLocation = WindowStartupLocation.Manual;
			}

			var screen = new Rect(
				SystemParameters.VirtualScreenLeft,
				SystemParameters.VirtualScreenTop,
				SystemParameters.VirtualScreenWidth,
				SystemParameters.VirtualScreenHeight);

			if (!screen.Contains(restoreTo))
			{
				if (!screen.Contains(restoreTo.Location))
				{
					restoreTo.Location = new Point();
				}

				if (restoreTo.Right > screen.Right)
				{
					restoreTo.Width = Math.Max(screen.Right - restoreTo.Left, window.MinWidth);
				}

				if (restoreTo.Bottom > screen.Bottom)
				{
					restoreTo.Height = Math.Max(screen.Bottom - restoreTo.Top, window.MinHeight);
				}
			}

			window.Top = restoreTo.Top;
			window.Left = restoreTo.Left;
			window.Width = restoreTo.Width;
			window.Height = restoreTo.Height;
		}

		private static void SetState(Window window, WindowState state)
		{
			Contract.Requires(window != null);

			// In testing, when the window's restored location is on a secondary monitor and its restored state
			// is maximized, the WindowState property had to be assigned after the window was loaded; otherwise, 
			// the window was maximized on the primary monitor as if its location was on the primary monitor.
			// However, changing the state after load causes the window to appear briefly in its normlized state 
			// before being maximized.  To avoid this I had tried hiding the window via its Visibility property 
			// first, but it didn't work correctly.
			RoutedEventHandler loaded = null;
			loaded = (sender, e) =>
			{
				window.Loaded -= loaded;
				window.WindowState = state;
			};

			window.Loaded += loaded;
		}
		#endregion

		#region Event Handlers
		private void SizeChanged(object sender, SizeChangedEventArgs e)
		{
			Contract.Requires(trackedWindow != null);

			if (trackedWindow.WindowState != WindowState.Maximized)
			{
				trackedLocation = new Rect(trackedWindow.Left, trackedWindow.Top, trackedWindow.Width, trackedWindow.Height);
			}
		}

		private void LocationChanged(object sender, EventArgs e)
		{
			Contract.Requires(trackedWindow != null);

			trackingLocation = trackedWindow.Dispatcher.BeginInvoke(
				(Action) (() =>
				{
					trackedLocation = new Rect(trackedWindow.Left, trackedWindow.Top, trackedWindow.Width, trackedWindow.Height);
				}),
				DispatcherPriority.ContextIdle);
		}

		private void StateChanged(object sender, EventArgs e)
		{
			if (trackingLocation != null)
			{
				trackingLocation.Abort();
			}
		}
		#endregion
	}
}